namespace Test.ConsoleProgram.Algorithm.Solution
{
    [TestDescription("算法: 0529. 扫雷游戏")]
    public class No0529_UpdateBoard : AbsBaseTestItem
    {
        /*
        让我们一起来玩扫雷游戏！
        给定一个代表游戏板的二维字符矩阵。 'M' 代表一个未挖出的地雷，'E' 代表一个未挖出的空方块，'B' 代表没有相邻（上，下，左，右，和所有4个对角线）地雷的已挖出的空白方块，数字（'1' 到 '8'）表示有多少地雷与这块已挖出的方块相邻，'X' 则表示一个已挖出的地雷。

        现在给出在所有未挖出的方块中（'M'或者'E'）的下一个点击位置（行和列索引），
        根据以下规则，返回相应位置被点击后对应的面板：
            如果一个地雷（'M'）被挖出，游戏就结束了- 把它改为 'X'。
            如果一个没有相邻地雷的空方块（'E'）被挖出，修改它为（'B'），并且所有和其相邻的未挖出方块都应该被递归地揭露。
            如果一个至少与一个地雷相邻的空方块（'E'）被挖出，修改它为数字（'1'到'8'），表示相邻地雷的数量。
            如果在此次点击中，若无更多方块可被揭露，则返回面板。

        注意：
            输入矩阵的宽和高的范围为 [1,50]。
            点击的位置只能是未被挖出的方块 ('M' 或者 'E')，这也意味着面板至少包含一个可点击的方块。
            输入面板不会是游戏结束的状态（即有地雷已被挖出）。
            简单起见，未提及的规则在这个问题中可被忽略。例如，当游戏结束时你不需要挖出所有地雷，考虑所有你可能赢得游戏或标记方块的情况。
        */

        public override void OnTest()
        {
            Assert.TestExe(UpdateBoard,
                new char[][] {
                    new char[] { 'E', 'E', 'E', 'E', 'E' },
                    new char[] { 'E', 'E', 'M', 'E', 'E' },
                    new char[] { 'E', 'E', 'E', 'E', 'E' },
                    new char[] { 'E', 'E', 'E', 'E', 'E' },
                },
                new int[] { 3, 0 },
                new char[][] {
                    new char[] { 'B', '1', 'E', '1', 'B' },
                    new char[] { 'B', '1', 'M', '1', 'B' },
                    new char[] { 'B', '1', '1', '1', 'B' },
                    new char[] { 'B', 'B', 'B', 'B', 'B' }
                });

            Assert.TestExe(UpdateBoard,
                new char[][] {
                    new char[] { 'B', '1', 'E', '1', 'B' },
                    new char[] { 'B', '1', 'M', '1', 'B' },
                    new char[] { 'B', '1', '1', '1', 'B' },
                    new char[] { 'B', 'B', 'B', 'B', 'B' },
                },
                new int[] { 1, 2 },
                new char[][] {
                    new char[] { 'B', '1', 'E', '1', 'B' },
                    new char[] { 'B', '1', 'X', '1', 'B' },
                    new char[] { 'B', '1', '1', '1', 'B' },
                    new char[] { 'B', 'B', 'B', 'B', 'B' },
                });
        }

        public char[][] UpdateBoard(char[][] board, int[] click)
        {
            int[] dirX = { 0, 1, 0, -1, 1, 1, -1, -1 };
            int[] dirY = { 1, 0, -1, 0, 1, -1, 1, -1 };
            void dfs(char[][] board, int x, int y)
            {
                int cnt = 0;
                for (int i = 0; i < 8; ++i)
                {
                    int tx = x + dirX[i];
                    int ty = y + dirY[i];
                    if (tx < 0 || tx >= board.Length || ty < 0 || ty >= board[0].Length)
                    {
                        continue;
                    }
                    // 不用判断 M，因为如果有 M 的话游戏已经结束了
                    if (board[tx][ty] == 'M')
                    {
                        ++cnt;
                    }
                }
                if (cnt > 0)
                {
                    // 规则 3
                    board[x][y] = (char)(cnt + '0');
                }
                else
                {
                    // 规则 2
                    board[x][y] = 'B';
                    for (int i = 0; i < 8; ++i)
                    {
                        int tx = x + dirX[i];
                        int ty = y + dirY[i];
                        // 这里不需要在存在 B 的时候继续扩展，因为 B 之前被点击的时候已经被扩展过了
                        if (tx < 0 || tx >= board.Length || ty < 0 || ty >= board[0].Length || board[tx][ty] != 'E')
                        {
                            continue;
                        }
                        dfs(board, tx, ty);
                    }
                }
            }

            int x = click[0], y = click[1];
            if (board[x][y] == 'M')
            {
                // 规则 1
                board[x][y] = 'X';
            }
            else
            {
                dfs(board, x, y);
            }
            return board;
        }
    }
}
